昨天提到Java會在編譯時實現自動裝箱和拆箱,將基礎類型和包裝類別進行轉換,同時將ByteCode進行反編譯,解讀程式碼實際運行的過程,證實使用包裝類別內建方法實現類型轉換,今日來使用JDK提供的編譯和反編譯工具,證實前幾篇提到很多次的觀念,有些「陳述式是特別設計」的使用方式,編譯後會照預設規則,以另一種方式執行
Java JDK提供了 javac(編譯)用來將.java檔案編譯成 bytecode,要了解實際內容可以使用javap(反編譯),解析bytecode轉成文字內容,裡面呈現了JVM可以呈現的指令及內容,現在來段簡單的程式碼實作
// 自訂一個 Class 在main方法輸入下列執行內容
public static void main(String[] args) {
int intNum = 99;
float floatNum = intNum;
System.out.println(floatNum); // output: 99.0
}
這段程式碼定義整數變數 intNum,接著將整數值賦值給浮點數 floatNum,打印 floatNum顯示 99.0,這有個問題Java是強型別,整數可以跟浮點數的類型轉換,後面會透過編譯工具來揭曉答案,先按照下面步驟產生內容
第一步將.java檔案進行編譯,打開 CMD 進入當前 .java檔案目錄,輸入 javac xxx.java執行完成,可以發現目錄下出現一個 .class檔案裡面就是編譯後的ByteCode
第二步透過反編譯查看編譯後的內容,javap -v (ByteCode檔案名稱) >> 要檢視的檔案名稱,執行成功可以看到新增了「要檢視的檔案名稱」,點進去就會看到「當前類別載入時」如何進行記憶體操作,以及main方法的運行流程
基礎類型除了能與封裝類型實現自動轉換,在「特定規則下」能實現基礎類型間的資料轉換,轉換有兩個大原則
看說明不太好理解,這邊先打開上個段落反編譯後的檔案,來看數值間的轉換方式
bipush 99
istore_1
iload_1
i2f
fstore_2
getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
fload_2
invokevirtual #13 // Method java/io/PrintStream.println:(F)V
return
其中實現流程可以發現,先建立整數常數 99,在存入記憶體#1中,在執行名為i2f的操作,後來去查資料發現是JVM將int轉成float的指令,因此後面的動作可以理解成,將整數轉化後的浮點數數值存入記憶體#2中,在實現將變數floatNum賦值操作,由結果可以知道基礎類型的資料轉型,在編譯時會加入型別轉換的指令
接著實作另一種情況,將佔用空間較大的類型,賦值給佔用空間較小的類型,基礎類型 float賦值給浮點數
float f2 = (float) 10.5;
int i2 = f2;
執行時會拋出這個錯誤,error: incompatible types: possible lossy conversion from float to int
要解決這個問題,可以透過強制轉型方式將float資料轉成整數,將程式碼改成下列做法
float f2 = (float) 10.5;
int i2 = (int) f2;
System.out.println(i2); // 10
// 重新反編譯內容
ldc #19 // float 10.5f
fstore_3
fload_3
f2i
istore 4
getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
iload 4
invokevirtual #20 // Method java/io/PrintStream.println:(I)V
從編譯結果可以知道,原本的i2f變成了f2i,用法如字面上的意思一樣,是將float轉成int,相較一開始將佔用空間小的型別,轉至佔用空間大的型別,會出現錯誤警示,其原因是每個型別佔用與最大值最小值,可能會因為要向下轉型的實際值,超出變數參考類型的範圍,導致出現預期外錯誤,下面來段範例說明,可能造成此問題的情況
long f3 = Long.MIN_VALUE;
int i3 = (int) f3;
System.out.printf( "Long Min Value:%d \nActual Value:%d\n", Long.MIN_VALUE, i3);
// output:
Long Min Value:-9223372036854775808
Actual Value:0
從結果仔細推導內容,Long.MIN_VALUE超出了int的最小值-2147483648,不在int類型可接受長度內,因此在轉型時會將整數i3的值,強制套用0,出現資料失控的問題,造成不可控的錯誤,Java為了防範這種問題,要求以強制轉型方式進行型別轉換
最後可以得到一種結論,即使程式在運編譯時有時候會幫忙自動轉型,實際撰寫邏輯時還是要養成檢查資料的習慣,才能預防出現即使程式編譯通過,實際執行卻出現失控的資料,導致很難抓的BUG出現